package vip.liux.backend.application.serviceImpl.pay;


import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.application.dots.PagedResultRequestDto;
import vip.liux.backend.application.service.pay.PayDemoOrderService;
import vip.liux.backend.application.service.pay.PayOrderService;
import vip.liux.backend.application.service.pay.PayRefundService;
import vip.liux.backend.application.service.pay.dto.order.PayOrderCreateDto;
import vip.liux.backend.application.service.pay.dto.order.PayOrderDto;
import vip.liux.backend.application.service.pay.dto.refund.PayRefundCreateDto;
import vip.liux.backend.application.service.pay.dto.refund.PayRefundDetailsDto;
import vip.liux.contracts.models.pay.PayDemoOrder;
import vip.liux.contracts.repositories.pay.PayDemoOrderRepository;
import vip.liux.contracts.shared.enums.pay.PayOrderStatus;
import vip.liux.contracts.shared.enums.pay.PayRefundStatus;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static org.apache.commons.lang3.ObjectUtils.notEqual;
import static vip.liux.application.PageKit.toPage;
import static vip.liux.application.PageKit.toPaged;
import static vip.liux.backend.infrastructure.utils.ServletUtil.getClientIP;
import static vip.liux.contracts.shared.enums.ErrorCode.*;
import static vip.liux.contracts.shared.util.ServiceExceptionUtil.exception;
import static vip.liux.core.utils.JsonUtils.toJson;


/**
 * 示例订单 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class PayDemoOrderServiceImpl implements PayDemoOrderService {

    /**
     * 接入的支付应用标识
     * <p>
     * 从 [支付管理 -> 应用信息] 里添加
     */
    private static final String PAY_APP_KEY = "demo";

    /**
     * 商品信息 Map
     * <p>
     * key：商品编号
     * value：[商品名、商品价格]
     */
    private final Map<Long, Object[]> spuNames = new HashMap<>();

    @Resource
    private PayOrderService payOrderApi;
    @Resource
    private PayRefundService payRefundApi;

    @Resource
    private PayDemoOrderRepository payDemoOrderMapper;

    public PayDemoOrderServiceImpl() {
        spuNames.put(1L, new Object[]{"华为手机", BigDecimal.valueOf(0.01)});
        spuNames.put(2L, new Object[]{"小米电视", BigDecimal.valueOf(0.10)});
        spuNames.put(3L, new Object[]{"苹果手表", BigDecimal.valueOf(1)});
        spuNames.put(4L, new Object[]{"华硕笔记本", BigDecimal.valueOf(10)});
        spuNames.put(5L, new Object[]{"蔚来汽车", BigDecimal.valueOf(2000)});
    }


    @Override
    public Long createDemoOrder(Long userId, Long spuId) {
        // 1.1 获得商品
        Object[] spu = spuNames.get(spuId);
        Assert.notNull(spu, "商品(" + spuId + ") 不存在");
        String spuName = (String) spu[0];
        BigDecimal price = (BigDecimal) spu[1];
        // 1.2 插入 demo 订单
        PayDemoOrder demoOrder = new PayDemoOrder().setUserId(userId)
                .setSpuId(spuId).setSpuName(spuName)
                .setPrice(price).setPayStatus(false).setRefundPrice(BigDecimal.ZERO);
        payDemoOrderMapper.save(demoOrder);

        // 2.1 创建支付单
        String payOrderNo = payOrderApi.createOrder(new PayOrderCreateDto()
                .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用
                .setMerchantOrderId(Objects.requireNonNull(demoOrder.getId()).toString()) // 业务的订单编号
                .setSubject(spuName).setBody("").setPrice(price) // 价格信息
                .setExpireTime(LocalDateTime.now().plus(Duration.ofHours(2L)))); // 支付的过期时间

        demoOrder.setPayOrderNo(payOrderNo);
        // 2.2 更新支付单到 demo 订单
        payDemoOrderMapper.save(demoOrder);
        return demoOrder.getId();
    }

    @Override
    public PayDemoOrder getDemoOrder(Long id) {
        return payDemoOrderMapper.findById(id).orElseThrow(() -> exception(DEMO_ORDER_NOT_FOUND));
    }

    @Override
    public PagedResultDto<PayDemoOrder> getDemoOrderPage(PagedResultRequestDto pageReqVO) {
        return toPaged(payDemoOrderMapper.findAll(toPage(pageReqVO)));
    }

    @Override
    public void updateDemoOrderPaid(Long id, String payOrderNo) {
        // 1.1 校验订单是否存在
        PayDemoOrder order = payDemoOrderMapper.findById(id).orElseThrow(() -> exception(DEMO_ORDER_NOT_FOUND));

        // 1.2 校验订单已支付
        if (order.getPayStatus()) {
            // 特殊：支付单号相同，直接返回，说明重复回调
            if (Objects.equals(order.getPayOrderNo(), payOrderNo)) {
                log.warn("[updateDemoOrderPaid][order({}) 已支付，且支付单号相同({})，直接返回]", order, payOrderNo);
                return;
            }
            // 异常：支付单号不同，说明支付单号错误
            log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({})，请进行处理！]",
                    order, payOrderNo);
            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
        }

        // 2. 校验支付订单的合法性
        PayOrderDto payOrder = validatePayOrderPaid(order, payOrderNo);

        order.setPayStatus(true).setPayTime(LocalDateTime.now())
                .setPayChannelCode(payOrder.getChannelCode());
        // 3. 更新 PayDemoOrderDO 状态为已支付
        payDemoOrderMapper.save(order);
    }

    /**
     * 校验支付订单的合法性
     *
     * @param order      交易订单
     * @param payOrderNo 支付订单编号
     * @return 支付订单
     */
    private PayOrderDto validatePayOrderPaid(PayDemoOrder order, String payOrderNo) {
        // 1. 校验支付单是否存在
        PayOrderDto payOrder = payOrderApi.getByOrderNo(payOrderNo);
        if (payOrder == null) {
            log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在，请进行处理！]", order.getId(), payOrderNo);
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        // 2.1 校验支付单已支付
        if (!PayOrderStatus.isSuccess(payOrder.getStatus())) {
            log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付，请进行处理！payOrder 数据是：{}]",
                    order.getId(), payOrderNo, toJson(payOrder));
            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
        }
        // 2.1 校验支付金额一致
        if (notEqual(payOrder.getPrice(), order.getPrice())) {
            log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配，请进行处理！order 数据是：{}，payOrder 数据是：{}]",
                    order.getId(), payOrderNo, toJson(order), toJson(payOrder));
            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
        }
        // 2.2 校验支付订单匹配（二次）
        if (notEqual(payOrder.getMerchantOrderId(), String.valueOf(order.getId()))) {
            log.error("[validatePayOrderPaid][order({}) 支付单不匹配({})，请进行处理！payOrder 数据是：{}]",
                    order.getId(), payOrderNo, toJson(payOrder));
            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
        }
        return payOrder;
    }

    @Override
    public void refundDemoOrder(Long id, String userIp) {
        // 1. 校验订单是否可以退款
        PayDemoOrder order = validateDemoOrderCanRefund(id);

        // 2.1 生成退款单号
        // 一般来说，用户发起退款的时候，都会单独插入一个售后维权表，然后使用该表的 id 作为 refundId
        //          这里我们是个简单的 demo，所以没有售后维权表，直接使用订单 id + "-refund" 来演示
        String refundId = order.getId() + "-refund";
        // 2.2 创建退款单
        String payRefundNo = payRefundApi.createRefund(new PayRefundCreateDto()
                .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用
                .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
                .setMerchantRefundId(refundId)
                .setReason("想退钱").setPrice(order.getPrice()));// 价格信息
        order.setPayRefundNo(payRefundNo).setRefundPrice(order.getPrice());
        // 2.3 更新退款单到 demo 订单
        payDemoOrderMapper.save(order);
    }

    private PayDemoOrder validateDemoOrderCanRefund(Long id) {
        // 校验订单是否存在
        PayDemoOrder order = payDemoOrderMapper.findById(id).orElseThrow(() -> exception(DEMO_ORDER_NOT_FOUND));
        // 校验订单是否支付
        if (!order.getPayStatus()) {
            throw exception(DEMO_ORDER_REFUND_FAIL_NOT_PAID);
        }
        // 校验订单是否已退款
        if (order.getPayRefundNo() != null) {
            throw exception(DEMO_ORDER_REFUND_FAIL_REFUNDED);
        }
        return order;
    }

    @Override
    public void updateDemoOrderRefunded(Long id, String refundId, String payRefundNo) {
        // 1. 校验并获得退款订单（可退款）
        PayRefundDetailsDto payRefund = validateDemoOrderCanRefunded(id, refundId, payRefundNo);
        PayDemoOrder order = payDemoOrderMapper.findById(id).orElseThrow(() -> exception(DEMO_ORDER_NOT_FOUND));
        order.setRefundTime(payRefund.getSuccessTime());
        // 2.2 更新退款单到 demo 订单
        payDemoOrderMapper.save(order);
    }

    private PayRefundDetailsDto validateDemoOrderCanRefunded(Long id, String refundId, String payRefundNo) {
        // 1.1 校验示例订单
        // 一般来说，这里应该用 refundId 来查询退款单，然后再校验订单是否匹配
        //       这里我们是个简单的 demo，所以没有售后维权表，直接使用订单 id 来查询订单
        PayDemoOrder order = payDemoOrderMapper.findById(id).orElseThrow(() -> exception(DEMO_ORDER_NOT_FOUND));

        // 1.2 校验退款订单匹配
        if (ObjectUtils.notEqual(order.getPayRefundNo(), payRefundNo)) {
            log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({})，请进行处理！order 数据是：{}]",
                    id, payRefundNo, toJson(order));
            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }

        // 2.1 校验退款订单
        PayRefundDetailsDto payRefund = payRefundApi.getRefundByRefundNo(payRefundNo);
        if (payRefund == null) {
            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
        }
        // 2.2 必须是退款成功状态
        if (!PayRefundStatus.isSuccess(payRefund.getStatus())) {
            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
        }
        // 2.3 校验退款金额一致
        if (notEqual(payRefund.getRefundPrice(), order.getPrice())) {
            log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配，请进行处理！order 数据是：{}，payRefund 数据是：{}]",
                    id, payRefundNo, toJson(order), toJson(payRefund));
            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
        }
        // 2.4 校验退款订单匹配（二次）
        if (notEqual(payRefund.getMerchantRefundId(), id + "-refund")) {
            log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({})，请进行处理！payRefund 数据是：{}]",
                    id, payRefundNo, toJson(payRefund));
            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }
        return payRefund;
    }
}
